﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
//using System.Windows.Shapes;
using Xceed.Wpf.Toolkit;
using System.Xml;
using System.Reflection;
using System.Timers;
using System.Windows.Threading;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using System.IO;
using System.Threading;
using System.ComponentModel;
using System.Security.Principal;


namespace Awesome_Shutdown_Timer
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class wndMain : Window
    {
        // Structures needed for the API calls
        private struct LUID
        {
            public int LowPart;
            public int HighPart;
        }
        private struct LUID_AND_ATTRIBUTES
        {
            public LUID pLuid;
            public int Attributes;
        }
        private struct TOKEN_PRIVILEGES
        {
            public int PrivilegeCount;
            public LUID_AND_ATTRIBUTES Privileges;
        }
        
        [DllImport("advapi32.dll")]
        static extern int OpenProcessToken(IntPtr ProcessHandle,
                             int DesiredAccess, out IntPtr TokenHandle);

        [DllImport("advapi32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool AdjustTokenPrivileges(IntPtr TokenHandle,
            [MarshalAs(UnmanagedType.Bool)]bool DisableAllPrivileges,
            ref TOKEN_PRIVILEGES NewState,
            UInt32 BufferLength,
            IntPtr PreviousState,
            IntPtr ReturnLength);

        [DllImport("advapi32.dll")]
        static extern int LookupPrivilegeValue(string lpSystemName,
                               string lpName, out LUID lpLuid);

        [DllImport("user32")]
        public static extern bool ExitWindowsEx(uint uFlags, uint dwReason);
        const uint EWX_LOGOFF = 0;
        const uint EWX_POWEROFF = 0x00000008;
        const uint EWX_REBOOT = 0x00000002;
        const uint EWX_SHUTDOWN = 0x00000001;
        const uint EWX_FORCE = 0x00000004;  // This flag has no effect if terminal services is enabled. Otherwise, the system does not send the WM_QUERYENDSESSION message. This can cause applications to lose data. Therefore, you should only use this flag in an emergency.
        const uint EWX_FORCEIFHUNG = 0x00000010;    // Forces processes to terminate if they do not respond to the WM_QUERYENDSESSION or WM_ENDSESSION message within the timeout interval. For more information, see the Remarks.

        [DllImport("Powrprof.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        public static extern bool SetSuspendState(bool hiberate, bool forceCritical, bool disableWakeEvent);

        //---

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern ExecutionState SetThreadExecutionState(ExecutionState esFlags);

        [FlagsAttribute]
        private enum ExecutionState : uint
        {
            EsAwaymodeRequired = 0x00000040,
            EsContinuous = 0x80000000,
            EsDisplayRequired = 0x00000002,
            EsSystemRequired = 0x00000001
        }

        //---

        public static string appPath;
        public static string appTitle = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
        public static string userSoundsDir = ""; // alarm sounds in the user profile
        public static string stockSoundsDir; // alarm sounds in the app's directory
        public bool portableMode;
        
        //---

        private int hours, minutes;
        private bool initializeComponentIsCompleted = false;   // will be TRUE after InitializeComponent()
        private string cfgFileName;

        private DateTime startTime;
        private DateTime finishTime;
        private System.Timers.Timer aTimer;
        private System.Timers.Timer specificTimeTabTimer;

        private struct Sound
        {
            public string fileName;
            public string displayName;

            public Sound(string filename)
            {
                this.fileName = filename;
                this.displayName = Path.GetFileNameWithoutExtension(filename);
            }

            public override string ToString()
            {
                return this.displayName;
            }
        }
        private struct MediaPlayerParams
        {
            public string fileName;
            public double volume;
            public int repeatCount;
            public int pauseBetweenRepeats;
        }
        private BackgroundWorker bgworkerPlayer = new BackgroundWorker();

        private bool isAlreadyDone_EnableSeShutdownPrivilege = false;
        private bool isAlreadyDone_StopAndResetTimer = true;

        //--------------------------------------------------
        //--------------------------------------------------

        public wndMain()
        {
            InitializeComponent();
            initializeComponentIsCompleted = true;

            // check if applicaton runs in portable mode and fill some global variables
            appPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
            portableMode = File.Exists(appPath + "\\portable");
            if (portableMode)
            {
                cfgFileName = appPath + "\\config.xml";
            }
            else
            {
                string appdataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\Awesome Shutdown Timer";
                cfgFileName = appdataDir + "\\config.xml";
                if (!Directory.Exists(appdataDir))
                    Directory.CreateDirectory(appdataDir);

                userSoundsDir = appdataDir + "\\Alarms";
                if (!portableMode && !Directory.Exists(userSoundsDir))
                    Directory.CreateDirectory(userSoundsDir);
                userSoundsDir += "\\";
            }
            stockSoundsDir = appPath + "\\Alarms";

            specificTimeTabTimer = new System.Timers.Timer(1000);    // Create a timer with a one second interval
            specificTimeTabTimer.Elapsed += new ElapsedEventHandler(OnSpecificTimeTabTimedEvent);    // Hook up the Elapsed event for the timer

            aTimer = new System.Timers.Timer(1000);    // Create a timer with a one second interval
            aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);    // Hook up the Elapsed event for the timer

            // check for updates
            BackgroundWorker bgworkerCheckForUpdates = new BackgroundWorker();
            bgworkerCheckForUpdates.DoWork += bgworkerCheckForUpdates_DoWork;
            bgworkerCheckForUpdates.RunWorkerCompleted += bgworkerCheckForUpdates_RunWorkerCompleted;
            bgworkerCheckForUpdates.RunWorkerAsync();

            // initialize background worker for alarm sounds playback
            bgworkerPlayer.WorkerSupportsCancellation = true;
            bgworkerPlayer.DoWork += bgworker_DoWork;
            bgworkerPlayer.RunWorkerCompleted += bgworker_RunWorkerCompleted;

            // load configuration and prepare GUI
            LoadConfig();
            LoadAlarmSoundsList();
            SetNewTime();
            cbAction_SelectionChanged(null, null);
            udRepeatSound_ValueChanged(null, null);

            // check admin rights
            bool isAdmin=IsAdministrator();
            BitmapImage img = new BitmapImage();
            img.BeginInit();
            img.UriSource = new Uri(isAdmin ? "res\\ok_x16.ico" : "res\\alert.ico", UriKind.Relative);
            img.EndInit();
            imgIsAdmin.Source = img;
            tbIsAdmin.Text = isAdmin ? "You app/script will be run with admin rights" : "You app/script will be run without admin rights!";

            // show version info
            this.Title = Assembly.GetExecutingAssembly().GetName().Name + " " +
                Assembly.GetExecutingAssembly().GetName().Version.Major.ToString() + "." +
                Assembly.GetExecutingAssembly().GetName().Version.Minor.ToString() + "." +
                Assembly.GetExecutingAssembly().GetName().Version.Build.ToString();
        }

        //--------------------------------------------------

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            //this.MinWidth = this.Width;
        }

        //--------------------------------------------------

        private void Window_Closed(object sender, EventArgs e)
        {
            SaveConfig();
        }

        //--------------------------------------------------

        #region bgworkerCheckForUpdates

        private void bgworkerCheckForUpdates_DoWork(object sender, DoWorkEventArgs e)
        {
            string LatestVersionXmlLocation = "http://awesome-shutdown-timer.googlecode.com/svn/wiki/LatestVersion.xml";
#if DEBUG
            LatestVersionXmlLocation = "D:\\projects\\csharp\\Awesome Shutdown Timer\\wiki\\LatestVersion.xml";
#endif
            UpdateInfo updateInfo = new UpdateInfo();
            e.Result = false;

            try
            {
                using (XmlReader reader = XmlReader.Create(LatestVersionXmlLocation, new XmlReaderSettings { IgnoreComments = true, IgnoreWhitespace = true }))
                {
                    reader.Read();  // skip declatation
                    reader.Read();  // LatestVersion node
                    reader.Read();  // Version
                    Version latestVersion = new Version(reader.ReadString());
                    reader.Read();  // UriInstaller
                    string uriInstaller = reader.ReadString();
                    reader.Read();  // UriPortable
                    string uriPortable = reader.ReadString();

                    if (latestVersion > Assembly.GetExecutingAssembly().GetName().Version)
                    {
                        // new version available!

                        updateInfo.latestVersion = latestVersion;
                        if (portableMode)
                            updateInfo.updateUri = uriPortable;
                        else
                            updateInfo.updateUri = uriInstaller;
                        
                        // reading What's new section
                        updateInfo.whatsNew = new WhatsNewEntry[0];
                        while (true)
                        {
                            reader.Read();
                            if (reader.Name != "WhatsNew") break;

                            WhatsNewEntry whatsNewEntry = new WhatsNewEntry();
                            whatsNewEntry.version = new Version(reader.GetAttribute("Version"));
                            string s = reader.ReadString();
                            whatsNewEntry.changes = System.Text.RegularExpressions.Regex.Split(s, "\\n").Select(p => p.Trim()).Where<string>(p => !string.IsNullOrEmpty(p)).ToArray();
                            Array.Resize(ref updateInfo.whatsNew, updateInfo.whatsNew.Length + 1);
                            updateInfo.whatsNew[updateInfo.whatsNew.Length - 1] = whatsNewEntry;
                        }

                        wndUpdate.updateInfo = updateInfo;  // save info
                        e.Result = true;
                    }
                }
            }
            catch
            {
                // skip exception (probably it's because of no internet connection)
            }
        }

        private void bgworkerCheckForUpdates_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            bool isUpdateAvailable = (bool)e.Result;
            if (isUpdateAvailable)
            {
                // new update available!
                cmdUpdateAvailable.Visibility = Visibility.Visible;
            }
        }

        #endregion  // bgworkerCheckForUpdates

        //--------------------------------------------------

        private void cmdUpdateAvailable_Click(object sender, RoutedEventArgs e)
        {
            new wndUpdate().ShowDialog(this);
        }

        //--------------------------------------------------

        private void LoadAlarmSoundsList()
        {
            if(!portableMode)
                foreach (string filename in Directory.EnumerateFiles(userSoundsDir, "*", SearchOption.TopDirectoryOnly)
                    .Where(s =>
                        s.EndsWith(".mp3", StringComparison.OrdinalIgnoreCase) ||
                        s.EndsWith(".wav", StringComparison.OrdinalIgnoreCase)))
                {
                    cbSounds.Items.Add(new Sound(filename));
                }

            foreach (string filename in Directory.EnumerateFiles(stockSoundsDir, "*", SearchOption.TopDirectoryOnly)
                .Where(s =>
                    s.EndsWith(".mp3", StringComparison.OrdinalIgnoreCase) ||
                    s.EndsWith(".wav", StringComparison.OrdinalIgnoreCase)))
            
            {
                cbSounds.Items.Add(new Sound(filename));
            }
        }

        //--------------------------------------------------

        /// <summary>
        /// Preventing computer from sleeping
        /// </summary>
        public static void PreventSleep()
        {
            SetThreadExecutionState(ExecutionState.EsContinuous | ExecutionState.EsSystemRequired);
        }

        /// <summary>
        /// Allow computer to enter a sleeping mode
        /// </summary>
        public static void AllowSleep()
        {
            SetThreadExecutionState(ExecutionState.EsContinuous);
        }

        //--------------------------------------------------

        public static IEnumerable<Object> FindAllChildren(DependencyObject depObj)
        {
            if (depObj != null)
                foreach (object child in LogicalTreeHelper.GetChildren(depObj))
                    // check if object is control, not the string
                    if (child != null && child.GetType().IsSubclassOf(typeof(System.Windows.UIElement)) == true)
                    {
                        yield return child;

                        foreach (Object childOfChild in FindAllChildren((DependencyObject)child))
                        {
                            yield return childOfChild;
                        }
                    }
        }

        //--------------------------------------------------

        /// <summary>
        /// Finds a Child of a given item in the logical tree. 
        /// </summary>
        /// <param name="parent">A direct parent of the queried item.</param>
        /// <param name="childName">x:Name or Name of child. </param>
        /// <returns>The first parent item that matches the submitted type parameter. 
        /// If not matching item can be found, a null parent is being returned.</returns>
        public static object FindChild(DependencyObject parent, string childName)
        {
            // Confirm parent and childName are valid. 
            if (parent == null) return null;

            foreach (object child in LogicalTreeHelper.GetChildren(parent))
            {
                // check if object is control, not the string
                if (child != null && child.GetType().IsSubclassOf(typeof(System.Windows.UIElement)) == true)
                {
                    if (child.GetType().GetProperty("Name") != null && ((FrameworkElement)child).Name == childName)
                        return child;   // match!

                    object childOfChild = FindChild((DependencyObject)child, childName);
                    if (childOfChild != null)
                        return childOfChild;    // match!
                }
            }

            return null;    // not found (
        }

        //--------------------------------------------------

        private void LoadConfig()
        {
            try
            {
                using (XmlReader reader = XmlReader.Create(cfgFileName, new XmlReaderSettings { IgnoreComments = true, IgnoreWhitespace = true }))
                {
                    reader.Read();  // skip declatation
                    
                    // check configuration file version
                    reader.Read();
                    string cfgVerOfCurrentFile = reader.GetAttribute("Version");
                    string cfgVerCorrect = "1.0";
                    string[] cfgVerSupported = new string[] { "1.0" };  // try to read such cfgfiles anyway
                    // accept configuration file only with correct major version (different minor versions are allowed though)
                    if ((cfgVerOfCurrentFile.Split('.')[0] != cfgVerCorrect.Split('.')[0]) && !cfgVerSupported.Contains(cfgVerOfCurrentFile))
                    {
                        throw new Exception(String.Format("WARNING: Configuration file version is invalid (must be {0}, not {1}), and all settings will be reset to defaults!", cfgVerCorrect, cfgVerOfCurrentFile));
                    }

                    while (true)
                    {
                        reader.Read();
                        if (reader.Name == "Config" && reader.NodeType == XmlNodeType.EndElement) break;  // end of config

                        switch (reader.Name)
                        {
                            case "MainWindowPosition":
                                // restore main window position
                                this.Top = Double.Parse(reader.GetAttribute("Top"));
                                this.Left = Double.Parse(reader.GetAttribute("Left"));
                                break;
                            case "Controls":
                                // restore controls statuses
                                while (true)
                                {
                                    reader.Read();
                                    if (reader.Name == "Controls" && reader.NodeType == XmlNodeType.EndElement) break;  // end of controls

                                    if (reader.NodeType == XmlNodeType.Element)
                                    {
                                        object o = FindChild(this, reader.GetAttribute("Name"));
                                        if (o != null)  // skip unknown controls
                                        {
                                            string s = o.GetType().ToString();
                                            switch (o.GetType().Name)
                                            {
                                                case "Expander":
                                                    ((Expander)o).IsExpanded = bool.Parse(reader.GetAttribute("IsExpanded"));
                                                    break;
                                                case "IntegerUpDown":
                                                    ((IntegerUpDown)o).Value = int.Parse(reader.GetAttribute("Value"));
                                                    break;
                                                case "CheckBox":
                                                    ((CheckBox)o).IsChecked = bool.Parse(reader.GetAttribute("IsChecked"));
                                                    break;
                                                case "TextBox":
                                                    ((TextBox)o).Text = reader.GetAttribute("Text");
                                                    break;
                                                case "WatermarkTextBox":
                                                    ((WatermarkTextBox)o).Text = reader.GetAttribute("Text");
                                                    break;
                                                case "RadioButton":
                                                    ((RadioButton)o).IsChecked = bool.Parse(reader.GetAttribute("IsChecked"));
                                                    break;
                                                case "Slider":
                                                    ((Slider)o).Value = int.Parse(reader.GetAttribute("Value"));
                                                    break;
                                                case "ComboBox":
                                                    ((ComboBox)o).SelectedIndex = int.Parse(reader.GetAttribute("SelectedIndex"));
                                                    break;
                                                case "TabControl":
                                                    ((TabControl)o).SelectedIndex = int.Parse(reader.GetAttribute("SelectedIndex"));
                                                    break;
                                            }
                                        }
                                    }
                                }
                                break;
                            default:
                                throw new Exception(String.Format("WARNING: Configuration file is invalid or damaged! Check your settings - they can be reset to defaults!"));
                        }
                    }
                }
            }
            catch (Exception e)
            {
#if DEBUG
                System.Windows.MessageBox.Show("ERROR: " + e.Message.ToString(),
                    appTitle,
                    MessageBoxButton.OK, MessageBoxImage.Error);
#endif
            }
        }

        //--------------------------------------------------

        private void SaveConfig()
        {
            try
            {
                // do not save states of this contols in the config file
                string[] controlsExclusions = new string[] { "txtResult", "txtCmdlineLong", "txtCmdlineShort" };

                using (XmlWriter writer = XmlWriter.Create(cfgFileName, new XmlWriterSettings { Encoding = Encoding.UTF8, Indent = true }))
                {
                    writer.WriteStartElement("Config");
                    writer.WriteAttributeString("Version", "1.0");

                    // save main window position
                    writer.WriteStartElement("MainWindowPosition");
                    writer.WriteAttributeString("Top", this.Top.ToString());
                    writer.WriteAttributeString("Left", this.Left.ToString());
                    writer.WriteEndElement();

                    // save controls states
                    writer.WriteStartElement("Controls");

                    foreach (object gb in FindAllChildren(this))
                    {
                        if (gb.GetType().GetProperty("Name") != null)
                            if (gb is GroupBox && !controlsExclusions.Contains(((FrameworkElement)gb).Name))
                            {
                                writer.WriteStartElement("GroupBox");
                                writer.WriteAttributeString("Name", ((FrameworkElement)gb).Name);

                                foreach (object ctrl in FindAllChildren((GroupBox)gb))
                                    if (!controlsExclusions.Contains(((FrameworkElement)ctrl).Name) && ((FrameworkElement)ctrl).Name != "")
                                        switch (ctrl.GetType().Name)
                                        {
                                            case "IntegerUpDown":
                                                writer.WriteStartElement("Control");
                                                writer.WriteAttributeString("Name", ((FrameworkElement)ctrl).Name);
                                                writer.WriteAttributeString("Value", ((IntegerUpDown)ctrl).Value.ToString());
                                                writer.WriteEndElement();
                                                break;
                                            case "CheckBox":
                                                writer.WriteStartElement("Control");
                                                writer.WriteAttributeString("Name", ((FrameworkElement)ctrl).Name);
                                                writer.WriteAttributeString("IsChecked", ((CheckBox)ctrl).IsChecked.ToString());
                                                writer.WriteEndElement();
                                                break;
                                            case "TextBox":
                                                writer.WriteStartElement("Control");
                                                writer.WriteAttributeString("Name", ((FrameworkElement)ctrl).Name);
                                                writer.WriteAttributeString("Text", ((TextBox)ctrl).Text);
                                                writer.WriteEndElement();
                                                break;
                                            case "WatermarkTextBox":
                                                writer.WriteStartElement("Control");
                                                writer.WriteAttributeString("Name", ((FrameworkElement)ctrl).Name);
                                                writer.WriteAttributeString("Text", ((WatermarkTextBox)ctrl).Text);
                                                writer.WriteEndElement();
                                                break;
                                            case "RadioButton":
                                                writer.WriteStartElement("Control");
                                                writer.WriteAttributeString("Name", ((FrameworkElement)ctrl).Name);
                                                writer.WriteAttributeString("IsChecked", ((RadioButton)ctrl).IsChecked.ToString());
                                                writer.WriteEndElement();
                                                break;
                                            case "Slider":
                                                writer.WriteStartElement("Control");
                                                writer.WriteAttributeString("Name", ((FrameworkElement)ctrl).Name);
                                                writer.WriteAttributeString("Value", Math.Round(((Slider)ctrl).Value, MidpointRounding.AwayFromZero).ToString());
                                                writer.WriteEndElement();
                                                break;
                                            case "ComboBox":
                                                writer.WriteStartElement("Control");
                                                writer.WriteAttributeString("Name", ((FrameworkElement)ctrl).Name);
                                                writer.WriteAttributeString("SelectedIndex", ((ComboBox)ctrl).SelectedIndex.ToString());
                                                writer.WriteEndElement();
                                                break;
                                            case "TabControl":
                                                writer.WriteStartElement("Control");
                                                writer.WriteAttributeString("Name", ((FrameworkElement)ctrl).Name);
                                                writer.WriteAttributeString("SelectedIndex", ((TabControl)ctrl).SelectedIndex.ToString());
                                                writer.WriteEndElement();
                                                break;
                                        }

                                writer.WriteEndElement();   // </GroupBox>
                            }
                    }

                    writer.WriteEndElement();   // </Controls>

                    writer.WriteEndElement();   // </Config>
                    writer.WriteEndDocument();
                    writer.Flush();
                }
            }
            catch (Exception e)
            {
                if (!portableMode)
                    System.Windows.MessageBox.Show("ERROR: Can't save configuration!\n\n" + e.Message.ToString(),
                        appTitle,
                        MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        //--------------------------------------------------

        private void SetNewTime()
        {
            if (!initializeComponentIsCompleted) return;

            int days;
            TimeSpan interval;
            string time = "";

            switch (tcWhen.SelectedIndex)
            {
                case 0: // when == after
                    days = hours / 24;
                    if (days != 0) time += String.Format("{0} day", days) + (days > 1 ? "s" : "") + " ";
                    time += String.Format("{0}:{1:00}", hours % 24, minutes);
                    lblTimeLeft.Content = time; 
                    lblSeconds.Content = ".00";
                    break;
                case 1: // when == specific time
                    if (dtSpecifiedTime.Value == null) dtSpecifiedTime.Value = DateTime.Now;
                    interval = (DateTime)dtSpecifiedTime.Value - DateTime.Now;
                    if (interval.Ticks < 0) interval = TimeSpan.Zero;
                    if (interval.Days != 0) time += String.Format("{0} day", interval.Days) + (interval.Days > 1 ? "s" : "") + " ";
                    time += String.Format("{0}:{1:00}", interval.Hours % 24, interval.Minutes);
                    lblTimeLeft.Content = time;
                    lblSeconds.Content = String.Format(".{0:00}", interval.Seconds);
                    break;
            }

        }

        //--------------------------------------------------

        private void rbHours_Checked(object sender, RoutedEventArgs e)
        {
            hours = int.Parse(((RadioButton)sender).Content.ToString());
            udHours.Value = hours;
            SetNewTime();
        }

        //--------------------------------------------------

        private void rbMinutes_Checked(object sender, RoutedEventArgs e)
        {
            minutes = int.Parse(((RadioButton)sender).Content.ToString());
            udMinutes.Value = minutes;
            SetNewTime();
        }

        //--------------------------------------------------

        private void udHours_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            hours = (int)e.NewValue;

            foreach (object o in FindAllChildren(spHours))
            {
                if (o.GetType().GetProperty("Name") != null)
                    if (o is RadioButton)
                    {
                        ((RadioButton)o).IsChecked = (hours == int.Parse(((RadioButton)o).Content.ToString()));
                    }
            }

            SetNewTime();
        }

        //--------------------------------------------------

        private void udMinutes_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            minutes = (int)e.NewValue;

            foreach (object o in FindAllChildren(spMinutes))
            {
                if (o.GetType().GetProperty("Name") != null)
                    if (o is RadioButton)
                    {
                        ((RadioButton)o).IsChecked = (minutes == int.Parse(((RadioButton)o).Content.ToString()));
                    }
            }

            SetNewTime();
        }

        //--------------------------------------------------

        private bool IsAdministrator()
        {
            //bool value to hold our return value
            bool isAdmin;
            try
            {
                //get the currently logged in user
                WindowsIdentity user = WindowsIdentity.GetCurrent();
                WindowsPrincipal principal = new WindowsPrincipal(user);
                isAdmin = principal.IsInRole(WindowsBuiltInRole.Administrator);
            }
            catch (UnauthorizedAccessException ex)
            {
                isAdmin = false;
            }
            catch (Exception ex)
            {
                isAdmin = false;
            }
            return isAdmin;
        }

        //--------------------------------------------------

        private void cbAction_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (!initializeComponentIsCompleted) return;

            spShutdownSleepHibernateLogoffRestart.Visibility = Visibility.Collapsed;
            spRunApp.Visibility = Visibility.Collapsed;
            spDisplayMessage.Visibility = Visibility.Collapsed;
            spPlaySound.Visibility = Visibility.Collapsed;

            switch (cbAction.SelectedIndex)
            {
                case 0: // shutdown
                case 1: // sleep
                case 2: // hibernate
                case 4: // restart
                    try
                    {
                        EnableSeShutdownPrivilege();
                    }
                    catch (Exception ee)
                    {
                        System.Windows.MessageBox.Show("ERROR: Can't enable SeShutdownPrivilege for the current process!\n" +
                            "As a result, " + appTitle + " will not be able to shutdown, restart, put to sleep or hibernate this PC!\n\n" +
                            ee.Message.ToString(),
                            appTitle, MessageBoxButton.OK, MessageBoxImage.Error);
                    }
                    spShutdownSleepHibernateLogoffRestart.Visibility = Visibility.Visible;
                    break;
                case 3: // logoff
                    spShutdownSleepHibernateLogoffRestart.Visibility = Visibility.Visible;
                    break;
                case 5: // run app
                    spRunApp.Visibility = Visibility.Visible;
                    break;
                case 6: // display message
                    spDisplayMessage.Visibility = Visibility.Visible;
                    break;
                case 7: // play sound
                    spPlaySound.Visibility = Visibility.Visible;

                    // check if we can create a MediaPlayer object
                    try
                    {
                        MediaPlayer player = new MediaPlayer();
                        player.Close();
                    }
                    catch (Exception ee)
                    {
                        System.Windows.MessageBox.Show("ERROR: " + ee.Message.ToString() + "/n/n" +
                            appTitle + " depends on Windows Media Player for a playback, so it's a good idea to check if it's all right with WMP.",
                            appTitle,
                            MessageBoxButton.OK, MessageBoxImage.Error);
                    }
                    
                    break;
            }
        }

        //--------------------------------------------------

        private void EnableSeShutdownPrivilege()
        {
            if (isAlreadyDone_EnableSeShutdownPrivilege) return;    // don't do this twice
            isAlreadyDone_EnableSeShutdownPrivilege = true;

            const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege";
            const short SE_PRIVILEGE_ENABLED = 2;
            const short TOKEN_ADJUST_PRIVILEGES = 32;
            const short TOKEN_QUERY = 8;
            IntPtr hToken;
            TOKEN_PRIVILEGES tkp;

            if (OpenProcessToken(Process.GetCurrentProcess().Handle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out hToken) == 0)
            {
                // get last win32 error message
                int errorCode = Marshal.GetLastWin32Error();
                string errorMessage = new Win32Exception(errorCode).Message;
                throw new System.Exception("OpenProcessToken() failed with error #" + errorCode.ToString() + ": " + errorMessage);
            }
            tkp.PrivilegeCount = 1;
            tkp.Privileges.Attributes = SE_PRIVILEGE_ENABLED;
            if(LookupPrivilegeValue("", SE_SHUTDOWN_NAME, out tkp.Privileges.pLuid)==0)
            {
                // get last win32 error message
                int errorCode = Marshal.GetLastWin32Error();
                string errorMessage = new Win32Exception(errorCode).Message;
                throw new System.Exception("LookupPrivilegeValue() failed with error #" + errorCode.ToString() + ": " + errorMessage);
            }
            if (!AdjustTokenPrivileges(hToken, false, ref tkp, 0U, IntPtr.Zero, IntPtr.Zero))
            {
                // get last win32 error message
                int errorCode = Marshal.GetLastWin32Error();
                string errorMessage = new Win32Exception(errorCode).Message;
                throw new System.Exception("AdjustTokenPrivileges() failed with error #" + errorCode.ToString() + ": " + errorMessage);
            }
        }

        //--------------------------------------------------

        #region bgworker_Player

        private void bgworker_DoWork(object sender, DoWorkEventArgs e)
        {
            // Get the BackgroundWorker that raised this event.
            BackgroundWorker worker = sender as BackgroundWorker;

            MediaPlayerParams mpp = (MediaPlayerParams)e.Argument;

            try
            {
                MediaPlayer player = new MediaPlayer();
                player.Open(new Uri(mpp.fileName, UriKind.Absolute));
                player.Volume = mpp.volume;
                TimeSpan pauseBetweenRepeats = TimeSpan.FromMilliseconds(mpp.pauseBetweenRepeats);

                // retrieving track length
                // Problem is, NaturalDuration cannot be determined until after MediaOpened has occurred, so we have to wait
                TimeSpan trackLength = TimeSpan.MinValue;
                DateTime dtStart = DateTime.Now;
                string errorMsg = "";
                while (DateTime.Now - dtStart < TimeSpan.FromSeconds(10))    // wait less than 10 seconds
                {
                    try
                    {
                        trackLength = player.NaturalDuration.TimeSpan;
                        break;
                    }
                    catch (Exception ee)
                    {
                        errorMsg = ee.Message.ToString();
                    };

                    Thread.Sleep(100);
                }
                if (trackLength == TimeSpan.MinValue)
                {
                    System.Windows.MessageBox.Show("ERROR: Can't get the track's length!\n\n" + errorMsg,
                        appTitle,
                        MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }

                // playing track mpp.repeatCount times
                for (int i = 0; i < mpp.repeatCount; i++)
                {
                    player.Stop();
                    player.Position = TimeSpan.MinValue;
                    player.Play();

                    // wait while track is playing
                    dtStart = DateTime.Now;
                    while (DateTime.Now - dtStart < trackLength)
                    {
                        Thread.Sleep(100);

                        if (worker.CancellationPending)
                        {
                            player.Stop();
                            player.Close();
                            return;
                        }
                    }

                    // pause between repeats
                    if (i != mpp.repeatCount)
                    {
                        dtStart = DateTime.Now;

                        while (DateTime.Now - dtStart < pauseBetweenRepeats)
                        {
                            Thread.Sleep(100);

                            if (worker.CancellationPending)
                                return;
                        }
                    }
                }
            }
            catch (Exception ee)
            {
                System.Windows.MessageBox.Show("ERROR: " + ee.Message.ToString() + "/n/n" +
                    appTitle + " depends on Windows Media Player for a playback, so it's a good idea to check if it's all right with WMP.",
                    appTitle,
                    MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }
        }

        //--------------------------------------------------

        private void bgworker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
            }
            else if (e.Error != null)
            {
                System.Windows.MessageBox.Show("ERROR: " + e.Error.Message,
                    appTitle,
                    MessageBoxButton.OK, MessageBoxImage.Error);
            }
            else
            {
                // normal completion
            }

            StopAndResetTimer();
        }

        #endregion  // bgworker

        //--------------------------------------------------

        private void PerformAnAction()
        {
            gbAction.IsEnabled = false;
            tbStatus.Text = "Performing an action...";

            switch(cbAction.SelectedIndex)
            {
                case 0: // shutdown
                    int errorCode;
                    ExitWindowsEx(EWX_SHUTDOWN | (cbForce.IsChecked == true ? EWX_FORCE : 0), 0);   // EWX_FORCEIFHUNG doesn't close a notepad with unsaved text
                    StopAndResetTimer();
                    break;
                case 1: // sleep
                    SetSuspendState(false, (cbForce.IsChecked == true), false);
                    StopAndResetTimer();
                    break;
                case 2: // hibernate
                    SetSuspendState(true, (cbForce.IsChecked == true), false);
                    StopAndResetTimer();
                    break;
                case 3: // log off
                    ExitWindowsEx(EWX_LOGOFF | (cbForce.IsChecked == true ? EWX_FORCE : 0), 0);   // EWX_FORCEIFHUNG doesn't close a notepad with unsaved text
                    StopAndResetTimer();
                    break;
                case 4: // restart
                    ExitWindowsEx(EWX_REBOOT | (cbForce.IsChecked == true ? EWX_FORCE : 0), 0);   // EWX_FORCEIFHUNG doesn't close a notepad with unsaved text
                    StopAndResetTimer();
                    break;
                case 5: // run app
                    ProcessStartInfo psi=new ProcessStartInfo(tbAppScriptFile.Text, tbArguments.Text);
                    psi.WorkingDirectory=tbWorkingDirectory.Text;
                    Process.Start(psi);
                    StopAndResetTimer();
                    break;
                case 6: // show message
                    System.Windows.MessageBox.Show(tbMessage.Text, 
                        Assembly.GetExecutingAssembly().GetName().Name, MessageBoxButton.OK, MessageBoxImage.Information);
                    break;
                case 7: // play sound
                    switch (cbSounds.SelectedIndex)
                    {
                        case 0:     // system beep
                            for (int i = 0; i < udRepeatSound.Value; i++)
                            {
                                System.Media.SystemSounds.Beep.Play();
                                System.Threading.Thread.Sleep((int)udPauseInBetween.Value);
                            }
                            StopAndResetTimer();
                            break;
                        default:    // mp3 or wav file
                            string fileName = ((Sound)cbSounds.SelectedItem).fileName;

                            MediaPlayerParams mpp = new MediaPlayerParams();
                            mpp.fileName = fileName;
                            mpp.volume = slVolume.Value / 100;
                            mpp.repeatCount = (int)udRepeatSound.Value;
                            mpp.pauseBetweenRepeats = (int)udPauseInBetween.Value;
                            bgworkerPlayer.RunWorkerAsync(mpp); // start playback

                            break;
                    }
                    break;
            }
        }

        //--------------------------------------------------

        private void StopAndResetTimer()
        {
            aTimer.Stop();
            SetNewTime();

            //if (player != null)
            //    player.Stop();
            bgworkerPlayer.CancelAsync();

            // disable preventing pc from sleeping
            AllowSleep();

            // change picture on the button
            BitmapImage logo = new BitmapImage();
            logo.BeginInit();
            logo.UriSource = new Uri("res\\Play_4_x48.ico", UriKind.Relative);
            logo.EndInit();
            imgGO.Source = logo;

            tbStatus.Text = "Ready";

            tcWhen_SelectionChanged(null, null);

            gbAction.IsEnabled = true;
            gbWhen.IsEnabled = true;

            isAlreadyDone_StopAndResetTimer = true;
        }

        //--------------------------------------------------

        public void ExecuteAction(Action a)
        {
            if (this.Dispatcher.CheckAccess())
                a.Invoke();
            else
                this.Dispatcher.Invoke(a, DispatcherPriority.ApplicationIdle);
        }

        //--------------------------------------------------

        // Specify what you want to happen when the Elapsed event is  raised
        private void OnTimedEvent(object source, ElapsedEventArgs e)
        {
            TimeSpan diff = (finishTime - e.SignalTime);
            if (diff.Ticks < 0)
            {
                diff = TimeSpan.Zero;
            }

            ExecuteAction(new Action(() =>
            {
                string time = "";
                if (diff.Days != 0) time += String.Format("{0} day", diff.Days) + (diff.Days > 1 ? "s" : "") + " ";
                time += String.Format("{0}:{1:00}", diff.Hours % 24, diff.Minutes);
                lblTimeLeft.Content = time;
                lblSeconds.Content = String.Format(".{0:00}", diff.Seconds);
            }));

            if (diff.Ticks < 500 * 10000)    // 500 msec
            {
                ((System.Timers.Timer)source).Stop();

                ExecuteAction(new Action(() => 
                {
                    PerformAnAction();
                    //StopAndResetTimer();
                }));
            }
        }

        //--------------------------------------------------

        private void OnSpecificTimeTabTimedEvent(object source, ElapsedEventArgs e)
        {
            ExecuteAction(new Action(() =>
            {
                SetNewTime();
            }));
        }

        //--------------------------------------------------

        private void cmdGO_Click(object sender, RoutedEventArgs e)
        {
            if (!aTimer.Enabled && isAlreadyDone_StopAndResetTimer)
            {
                //gbAction.IsEnabled = false;
                gbWhen.IsEnabled = false;
                specificTimeTabTimer.Stop();

                // change picture on the button
                BitmapImage logo = new BitmapImage();
                logo.BeginInit();
                logo.UriSource = new Uri("res\\Stop Alt_x48.ico", UriKind.Relative);
                logo.EndInit();
                imgGO.Source = logo;

                tbStatus.Text = "Timer is running...";

                // prevent pc from sleeping (if needed)
                if (cbKeepPCOn.IsChecked == true)
                    PreventSleep();

                startTime = DateTime.Now;
                switch (tcWhen.SelectedIndex)
                {
                    case 0: // when == after
                        finishTime = ((startTime.AddHours((double)udHours.Value)).AddMinutes((double)udMinutes.Value).AddMilliseconds(100));
                        break;
                    case 1: // when == specific time
                        finishTime = (DateTime)dtSpecifiedTime.Value;
                        break;
                }

                isAlreadyDone_StopAndResetTimer = false;
                aTimer.Start();
            }
            else
            {
                StopAndResetTimer();
            }
        }

        //--------------------------------------------------

        private void dtSpecifiedTime_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            SetNewTime();
        }

        private void tcWhen_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            SetNewTime();
            if (tcWhen.SelectedIndex == 1)
                specificTimeTabTimer.Start();
            else
                specificTimeTabTimer.Stop();
        }

        private void cmdSetToNow_Click(object sender, RoutedEventArgs e)
        {
            dtSpecifiedTime.Value = DateTime.Now;
        }

        private void cmdSelectAppScriptFile_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog openDialog = new OpenFileDialog();
            if (openDialog.ShowDialog().Value)
            {
                tbAppScriptFile.Text = openDialog.FileName;
            }
        }

        private void cmdSelectWorkingDirectory_Click(object sender, RoutedEventArgs e)
        {
            var dialog = new System.Windows.Forms.FolderBrowserDialog();
            if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                tbWorkingDirectory.Text = dialog.SelectedPath;
            }
        }

        //--------------------------------------------------

        private void cbKeepPCOn_CheckedOrUnchecked(object sender, RoutedEventArgs e)
        {
            if (aTimer != null && aTimer.Enabled)
            {
                if (cbKeepPCOn.IsChecked == true)
                    PreventSleep();
                else
                    AllowSleep();
            }
        }

        //--------------------------------------------------

        private void cmdAbout_Click(object sender, RoutedEventArgs e)
        {
            new wndAbout().ShowDialog(this);
        }

        //--------------------------------------------------

        private void cmdManageSounds_Click(object sender, RoutedEventArgs e)
        {
            if(portableMode)
                Process.Start(new ProcessStartInfo(stockSoundsDir));
            else
                Process.Start(new ProcessStartInfo(userSoundsDir));
        }

        //--------------------------------------------------

        private void udRepeatSound_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            if (!initializeComponentIsCompleted) return;

            udPauseInBetween.IsEnabled = udRepeatSound.Value > 1;
        }

        //--------------------------------------------------

        public void hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
        {
            if (e.Uri.IsAbsoluteUri)
            {
                // open a webpage

                Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
            }
            else
            {
                // open a file

                string eula = wndMain.appPath + "\\" + e.Uri.OriginalString;
                if (!File.Exists(eula))
                {
                    System.Windows.MessageBox.Show("ERROR: License file \"" + e.Uri.OriginalString + "\" is missing!",
                        wndMain.appTitle,
                        MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }

                Process.Start(new ProcessStartInfo(eula));
            }

            e.Handled = true;
        }
    }
}
